package wx

import (
	"context"
	"encoding/json"
	"errors"
	"log/slog"
	"os"
	"strings"
	"time"

	"gitee.com/gxcc/go-utils/utils"

	"gitee.com/gxcc/go-thirds/cache"
)

// WxmpSvc 微信小程序
type WxmpSvc struct {
	AppId     string
	AppSecret string
}

type WxmpUserAuthResult struct {
	Errcode    int    `json:"errcode"`
	Errmsg     string `json:"errmsg"`
	OpenId     string `json:"openid"`
	UnionId    string `json:"unionid"`
	SessionKey string `json:"session_key"`
}

type WxmpPhoneInfo struct {
	PhoneNumber     string `json:"phoneNumber"`     // 用户绑定的手机号（国外手机号会有区号）
	PurePhoneNumber string `json:"purePhoneNumber"` // 没有区号的手机号
	CountryCode     string `json:"countryCode"`     // 区号
}

func (w *WxmpSvc) reloadAccessToken() (string, error) {
	reqUrl := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + w.AppId + "&secret=" + w.AppSecret
	res, err := utils.HttpGet(reqUrl, nil)
	if err != nil {
		slog.Error(err.Error())
		return "", err
	}

	type Info struct {
		AccessToken string `json:"access_token"`
		ExpiresIn   int    `json:"expires_in"`
	}
	var info Info
	if err := json.Unmarshal(res.Body, &info); err != nil {
		slog.Error(err.Error())
		return "", err
	}
	if len(info.AccessToken) == 0 {
		return "", errors.New("获取微信小程序平台accessToken失败，返回结果：" + string(res.Body))
	}

	// accessToken存放到redis中
	var ctx = context.Background()
	var accessTokenRedisKey = "wxmp_access_token_" + w.AppId
	cache.RedisClient.Set(ctx, accessTokenRedisKey, info.AccessToken, time.Duration(info.ExpiresIn-60)*time.Second)

	slog.Debug("reloadAccessToken", "accessToken: ", info.AccessToken)
	return info.AccessToken, nil
}

// GetAccessToken 获取AccessToken
func (w *WxmpSvc) GetAccessToken() (string, error) {
	var ctx = context.Background()
	var accessTokenRedisKey = "wxmp_access_token_" + w.AppId
	accessToken, err := cache.RedisClient.Get(ctx, accessTokenRedisKey).Result()
	if err != nil || len(accessToken) == 0 {
		return w.reloadAccessToken()
	}
	return accessToken, nil
}

// GetUnlimitedQrCode 获取小程序码
// envVersion：要打开的小程序版本。正式版为 "release"，体验版为 "trial"，开发版为 "develop"。默认是正式版。
func (w *WxmpSvc) GetUnlimitedQrCode(envVersion string, scene string, page string, filePath string) error {
	accessToken, err := w.GetAccessToken()
	if err != nil {
		return err
	}
	reqUrl := "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken

	var bodyMap = map[string]any{
		"scene": scene,
	}
	if len(page) > 0 {
		bodyMap["page"] = page
	}
	if len(envVersion) > 0 {
		bodyMap["env_version"] = envVersion
	}

	reqBody, _ := json.Marshal(bodyMap)
	res, err := utils.HttpPost(reqUrl, nil, reqBody)
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	if strings.ToLower(res.Header.Get("content-type")) == "application/json" ||
		len(res.Body) < 1024 {
		type Result struct {
			Errcode int    `json:"errcode"`
			Errmsg  string `json:"errmsg"`
		}
		var result Result
		if err := json.Unmarshal(res.Body, &result); err != nil {
			slog.Error(err.Error())
			return err
		}
		return errors.New(result.Errmsg)
	} else {
		// 文件夹
		err := os.WriteFile(filePath, res.Body, os.ModePerm)
		if err != nil {
			slog.Error(err.Error())
			return err
		}
		return nil
	}
}

// CodeToSessionKey code换sessionKey
func (w *WxmpSvc) CodeToSessionKey(code string) (WxmpUserAuthResult, error) {
	reqUrl := "https://api.weixin.qq.com/sns/jscode2session?appid=" + w.AppId + "&secret=" + w.AppSecret +
		"&js_code=" + code + "&grant_type=authorization_code"
	res, err := utils.HttpGet(reqUrl, nil)
	if err != nil {
		slog.Error(err.Error())
		return WxmpUserAuthResult{}, err
	}

	var authResult WxmpUserAuthResult
	if err := json.Unmarshal(res.Body, &authResult); err != nil {
		slog.Error(err.Error())
		return WxmpUserAuthResult{}, err
	}
	if authResult.Errcode != 0 {
		return WxmpUserAuthResult{}, errors.New("获取微信小程序会话密匙失败：" + authResult.Errmsg)
	}
	return authResult, nil
}

// GetUserPhoneNumber 解密获取用户手机号
func (w *WxmpSvc) GetUserPhoneNumber(encryptedData string, iv string, sessionKey string) (string, error) {
	pc := WxBizDataCrypt{
		AppID:      w.AppId,
		SessionKey: sessionKey,
	}

	slog.Info("encrypted data: " + encryptedData)
	slog.Info("iv: " + iv)
	slog.Info("sessionKey: " + sessionKey)

	res, err := pc.Decrypt(encryptedData, iv, true)
	if err != nil {
		slog.Error(err.Error())
		return "", err
	}

	var phoneInfo WxmpPhoneInfo
	if err := json.Unmarshal([]byte(res.(string)), &phoneInfo); err != nil {
		slog.Error(err.Error())
		return "", err
	}
	return phoneInfo.PurePhoneNumber, nil
}

// GetUserPhoneNumber2 解密获取用户手机号
func (w *WxmpSvc) GetUserPhoneNumber2(code string) (string, error) {
	accessToken, err := w.GetAccessToken()
	if err != nil {
		return "", err
	}
	reqUrl := "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=" + accessToken

	var bodyMap = map[string]any{
		"code": code,
	}

	reqBody, _ := json.Marshal(bodyMap)
	res, err := utils.HttpPost(reqUrl, nil, reqBody)
	if err != nil {
		slog.Error(err.Error())
		return "", err
	}

	type Result struct {
		ErrCode   int           `json:"errcode"`
		ErrMsg    string        `json:"errmsg"`
		PhoneInfo WxmpPhoneInfo `json:"phone_info"`
	}
	var result Result
	if err := json.Unmarshal(res.Body, &result); err != nil {
		slog.Error(err.Error())
		return "", err
	}
	return result.PhoneInfo.PurePhoneNumber, nil

}

// convertTelMask 加电话掩码
func (w *WxmpSvc) convertTelMask(tel string) string {
	tel = strings.TrimSpace(tel)
	if len(tel) >= 8 {
		// 转为类似格式：+86-177****1234
		tel = tel[:len(tel)-8] + "****" + tel[len(tel)-4:]
	}
	return tel
}

// UploadShippingInfo 发货信息录入
func (w *WxmpSvc) UploadShippingInfo(wxPayTransactionId string, logisticsType int, deliveryMode int, isAllDelivered bool,
	shippingList []WxShippingItem, consignorTel string, receiverTel string, uploadTime time.Time, payerOpenId string) error {

	// 寄件电话、收件电话处理
	consignorTel = w.convertTelMask(consignorTel)
	receiverTel = w.convertTelMask(receiverTel)

	for i := range shippingList {
		if len(shippingList[i].Contact.ConsignorContact) > 0 {
			shippingList[i].Contact.ConsignorContact = w.convertTelMask(shippingList[i].Contact.ConsignorContact)
		} else if len(consignorTel) > 0 {
			shippingList[i].Contact.ConsignorContact = consignorTel
		}
		if len(shippingList[i].Contact.ReceiverContact) > 0 {
			shippingList[i].Contact.ReceiverContact = w.convertTelMask(shippingList[i].Contact.ReceiverContact)
		} else if len(receiverTel) > 0 {
			shippingList[i].Contact.ReceiverContact = receiverTel
		}
	}

	accessToken, err := w.GetAccessToken()
	if err != nil {
		return err
	}
	reqUrl := "https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token=" + accessToken

	var bodyMap = map[string]any{
		"order_key": map[string]any{
			"order_number_type": 2,
			"transaction_id":    wxPayTransactionId,
		},
		"logistics_type":   logisticsType,
		"delivery_mode":    deliveryMode,
		"is_all_delivered": isAllDelivered,
		"shipping_list":    shippingList,
		"upload_time":      uploadTime.Format(time.RFC3339),
		"payer": map[string]any{
			"openid": payerOpenId,
		},
	}

	reqBody, _ := json.Marshal(bodyMap)
	res, err := utils.HttpPost(reqUrl, nil, reqBody)
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	type Result struct {
		ErrCode int    `json:"errcode"`
		ErrMsg  string `json:"errmsg"`
	}
	var result Result
	if err := json.Unmarshal(res.Body, &result); err != nil {
		slog.Error(err.Error())
		return err
	}

	if result.ErrCode != 0 {
		slog.Error(string(res.Body))
		return errors.New(result.ErrMsg)
	}
	return nil
}

// NotifyConfirmReceive 确认收货提醒
func (w *WxmpSvc) NotifyConfirmReceive(wxPayTransactionId string, wxMchId string, outTradeNo string, receivedTime time.Time) error {
	accessToken, err := w.GetAccessToken()
	if err != nil {
		return err
	}
	reqUrl := "https://api.weixin.qq.com/wxa/sec/order/notify_confirm_receive?access_token=" + accessToken

	var bodyMap = map[string]any{
		"transaction_id":    wxPayTransactionId,
		"merchant_id":       wxMchId,
		"merchant_trade_no": outTradeNo,
		"received_time":     receivedTime.Unix(),
	}

	reqBody, _ := json.Marshal(bodyMap)
	res, err := utils.HttpPost(reqUrl, nil, reqBody)
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	type Result struct {
		ErrCode int    `json:"errcode"`
		ErrMsg  string `json:"errmsg"`
	}
	var result Result
	if err := json.Unmarshal(res.Body, &result); err != nil {
		slog.Error(err.Error())
		return err
	}

	if result.ErrCode != 0 {
		slog.Error(string(res.Body))
		return errors.New(result.ErrMsg)
	}
	return nil
}

// SendMessage 发送订阅消息
// state：跳转小程序类型：developer为开发版；trial为体验版；formal为正式版；默认为正式版
func (w *WxmpSvc) SendMessage(touser, template_id, page string, data map[string]interface{}, state string) error {
	accessToken, err := w.GetAccessToken()
	if err != nil {
		return err
	}
	url := "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + accessToken

	var bodyMap = map[string]interface{}{
		"touser":      touser,
		"template_id": template_id,
		"page":        page,
		"data":        data,
		"state":       state,
	}

	reqBody, _ := json.Marshal(bodyMap)
	slog.Debug(string(reqBody))

	res, err := utils.HttpPost(url, nil, reqBody)
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	type Result struct {
		Errcode int    `json:"errcode"`
		Errmsg  string `json:"errmsg"`
	}
	var result Result
	if err := json.Unmarshal(res.Body, &result); err != nil {
		slog.Error(err.Error())
		return err
	}
	if result.Errcode != 0 {
		slog.Error(result.Errmsg)
		return errors.New(result.Errmsg)
	}
	return nil
}
